package com.menghao.rpc.consumer.handle;

import com.menghao.rpc.exception.InitializationException;
import com.menghao.rpc.exception.InvokeException;
import lombok.Getter;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.MessageFormat;

/**
 * <p>JDK动态代理工厂</br>
 * <p>对@Reference标识的成员变量进行动态代理，最终进行Rpc调用</p>
 *
 * @author MarvelCode
 * @see ReferenceAgent
 */
public class JdkProxyFactory {

    /**
     * 根据@Reference创建代理对象
     *
     * @param referenceAgent 实际处理对象
     * @return 代理后的对象
     */
    public <T> T getProxy(ReferenceAgent referenceAgent) {
        return new RpcProxy<T>(referenceAgent).getProxy();
    }

    private static class RpcProxy<T> implements InvocationHandler {

        private ReferenceAgent referenceAgent;

        @Getter
        private T proxy;

        private static Method hashcodeMethod;
        private static Method toStringMethod;
        private static Method equalsMethod;

        static {
            try {
                hashcodeMethod = Object.class.getMethod("hashCode");
                toStringMethod = Object.class.getMethod("toString");
                equalsMethod = Object.class.getMethod("equals", Object.class);
            } catch (NoSuchMethodException e) {
                throw new InitializationException(e);
            }
        }

        @SuppressWarnings("unchecked")
        RpcProxy(ReferenceAgent referenceAgent) {
            this.referenceAgent = referenceAgent;
            proxy = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{referenceAgent.getSourceInterface()}, this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Object.class) {
                if (hashcodeMethod == method) {
                    return System.identityHashCode(proxy);
                }
                if (toStringMethod == method) {
                    return referenceAgent.toString();
                }
                if (equalsMethod == method) {
                    return proxy == args[0];
                }
                return new InvokeException(MessageFormat.format("method {0} not support", method.getName()));
            }
            return referenceAgent.invoke(method, args);
        }
    }
}
